Repository navigation
feat(mcp-server): RFC-0045 step 5, references and repeaters over MCP - #216
Conversation
- list_collections: fieldsSchemaStrict and strictCost beside fieldsSchema.
- create_entry/update_entry: fieldsStrict (null leaves a value as it is,
item nulls stripped, all-null items dropped and reported in droppedItems)
and clearFields, with the RFC's edge cases.
- Prechecks run the store's own write plan as a dry run
(NodeWriteOptions.dryRun, deleteContentEntry dryRun): reference targets,
repeater items and delete_entry's entry_referenced refusal come from the
store, not a parallel checker.
- Approval prompts name reference targets and repeater item changes.
- New read-only list_references (to/from); the API's referrers read moves
to core-ts (listReferrers) and both use it. list_entries returns groupId.
- Translation tools: localized repeater text by item path.
- Operation tools: sub, collections, maxItems, subFields, itemLabel, and
onInvalid "clear" on a continuation.
- update_collection/change_field_type offer the two types only when the
store has fieldTypes (defineTools({ referenceTypes })).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
1. save_translation folds item paths into the translation's own list (else a copy of the source's), keeping its targets, images, selects and text not resent; a list of another length is translation_shape_mismatch. 2. Item paths need get_translation_source's sourceRev (conflict when the source moved on); the handler refuses paths that no longer fit. 3. Approval prompts describe the dry run's planned entry (canonical ids). 4. During rename_field, removing the new or the old key removes both. 5. A continuation's onInvalid "clear" applies to its report (cleared, no refusal); MCP's continuation headline is the stored operation's. 6. The entry dry run returns after the hold, gates, assets and terms checks; the prechecks build the same entry the handler writes. 7. list_references clamps offset like the API route. 8. Repeater prompts show an item changed in place as "changed item N". Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
|
Review round 1 fixed in 209770d (all eight findings):
Tests: Local checks: lint, types, size and docs are clean, and mcp-server, api, cli, astro-renderer and cloudflare tests all pass. In core-ts, every SQLite and DO suite passes; the D1 variants of 🤖 Generated with Claude Code |
1. A translation's own list is a base for item paths only when it was made
from the source's current revision; otherwise a partial save is
translation_shape_mismatch ("send the whole list").
2. A key is an item path only when its first part names a repeater of the
collection (itemPathRepeater); og/image in an untyped collection stays a
plain key, as on main.
3. Prompts compare items as canonical JSON, note reorders whenever matched
items change order, and read the stored list through the markers.
4. During rename_field the old and new key are one field: setting one while
clearing the other is invalid; null entries describe the planned value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
|
Review round 2 fixed in 4ff3347:
"Amended in 5" and the changeset are updated. Local checks: types, repo-wide lint and size are clean; the whole mcp-server suite (198) passes, as do core-ts's field tests. The changes are limited to mcp-server and core-ts's pure translation helpers. 🤖 Generated with Claude Code |
1. Item paths write only into a copy of the source's list. Once the same-locale translation holds its own list for a repeater, paths are refused (translation_shape_mismatch, "send the whole list"); the rev-based base selection (madeFromCurrent) is gone. 2. Approval prompts read the stored values through the markers with the store's own normalizeEntryFields (now exported), so an edition member is not treated as a translation. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
|
Review round 3 fixed in c5785bb:
"Amended in 5" and the changeset are updated. Local checks: types, repo-wide lint, size and the tool docs are clean; mcp-server's whole suite (199 tests) and core-ts's field tests pass. 🤖 Generated with Claude Code |
RFC-0045 rollout step 5: reference and repeater fields over MCP (§6, §9 step 5). Both types stay refused on every production write path until step 7 (
fields_unsupported), and the production tool list does not offer them; the shared suites and this PR's tests enable them withSiteGraphStoreOptions.fieldTypes.What changes
list_collectionsreturnsfieldsSchema(open),fieldsSchemaStrictandstrictCost(from step 1'sfieldsJsonSchema/strictSchemaCost). Reference descriptions name the target collections by their labels.create_entry/update_entry:fieldsStrictinstead offields(invalidif both).nullor a missing key means "leave as it is" (on create: no value).nullsub-values in repeater items are stripped; all-null items are dropped and reported indroppedItems. Read by core-ts's new purereadStrictFields(fields/strict-input.ts).clearFieldson update, with either input: set and clear of one key →invalid; unknown key →unknown_field; on create →invalid.NodeWriteOptions.dryRunoncreateContentEntry/updateContentEntry, anddeleteContentEntry(entry, { dryRun }): the store'sEntryEngineplans the write and throws every plan-time refusal (values, assets,requiredToPublish, translation links, canonical ids, target guard,where, own group, the 100-references cap,planLeave), then returns without writing. MCP's copy of the field checks (checkEntryFields) is gone;checkEntryWritecalls the dry run.delete_entryrefuses in precheck withentry_referencedand the store'sdetails.referrers(published referrers, up to 20, with the total).field-prompts.ts):"Venue" (venue) → "Main hall" (entry_…, Places),"Speakers" (speakers): 2 items → 3 items (added "Ada Lovelace" (entry_…, People), Speaker), removed items, reorders; titles and text quoted and truncated withquote().list_references { siteId, entryId, direction: "to" | "from" }(content:read). The API's referrers read moved fromapi/src/routes/entry-references.tsinto core-ts (references/referrers.ts:listReferrers,listReferenceTargets,referenceCard); the API route and the tool both call it. Same site check as the API (another site's entry →not_found), titles marked as content (notice). 110 → 111 tools.list_entriesreturnsgroupId.whereon a reference field stays refused (invalid_query, tested) until step 8.speakers/0/talk) inget_translation_source, folded back bysave_translationinto a copy of the source's list (core-tstranslatedItemValues); bad paths refused in precheck; shared repeatersfield_not_localized.sub(rename, retype, remove),collections,maxItems,subFields,itemLabelonchange_field_type,onInvalid: "clear"on a continuation,unconvertiblein results; headlines name the sub-field.update_collection's input:defsClosurewas already in place from step 1. The offered types now follow the store's capability:defineTools({ referenceTypes }),defaultToolList(referenceTypes), andcreateCloudhalMcpServerpassesstore.fieldTypes !== undefined. The resolving-$refs test now covers both tool lists.change_field_type'stypeenum follows the same switch.node scripts/mcp-tools-doc.tsregeneratedpackages/mcp-server/README.md. RFC "Amended in 5" added. Changesetrfc45-step5-mcp.md.Deviations / decisions to check (also in the RFC's "Amended in 5")
fieldTypes, notsupported(siteId): tools are listed before any site is named. The store still decides per site. Production passes none before step 7.not_found),content:read, no status gates (an editor's view of the working copy, like the API). If you meant something stricter, tell me.list_references"from" reads the entry's ownentry_refsrows, plus its same-site original's rows it shows by thesharedrule (inherited: true), each with the group's members (any status).fieldsStrictisunknown_fieldeven whennull, so a typo is never silently "left as it is". An item that is already{}as given is not a strict drop (the checker's "no empty item" refuses it).set_field_localizedhas nosubinput over MCP (the store refuses it).Tests
mcp-server/test/references.test.ts(new, 11 cases): list_collections schemas +strictCost; the strict round trip (create with nulls stores nothing; onlyvenuechanges leavestartsAt/speakers; subsets; item nulls stripped, all-null items dropped and reported; clearFields); every refusal (set-and-clear, unknown keys, clearFields on create, both inputs) before anyone is asked; reference prechecks (other site, wrong collection, missing, repeater item); approval prompts;delete_entryrefusal vs a draft referrer;list_referencesboth ways + site scoping +whererefusal +groupId; translation item paths; operation inputs (subrename, retype headline, text → reference withcollections, continuation stuck on an archived target, thenonInvalid: "clear"); production gate (nofieldTypes: tool lists without the types,update_collectionandchange_field_typeto reference/repeater →fields_unsupported).entry-dry-run.test.ts(dry runs refuse and write nothing),fields-strict-input.test.ts,fields-translation.test.ts(repeater paths, 100k-character path).tool-schemas(both tool lists),tool-order,tools,e2e(111 tools),entry-fields-assets(now through the dry run),fields,translation-tools,collections,listings, APIreferences-api(sqlite + D1).pnpm checkgreen locally on this commit (20/20 and 19/19 turbo tasks, 11m54s).🤖 Generated with Claude Code